(function($) {

  $.fn.editablelist = function(options) 
  { 
    options = jQuery.extend({
      placeholder:'add an item', // either this or @placeholder of an item
      maxlength: 256,            // either this or @maxlength of an item
      delimiter:',',             // delimeter of @value list
      unique:true,               // list may contain only unique items
      backspace:true,            // BACKSPACE can be used to delete items in the list
      validator: function(text) { return true; }
    }, options);
    
    function init() 
    {
    
      var $this = $(this);
      var settings = jQuery.extend({},options);
      
      $this.addClass("editable-list");
      
      var addtag_styles = "position:relative;";
      var addtag_input_styles = "position:absolute; left:0; top:0; right:0; margin:0px; font: inherit; border:1px solid transparent; padding:5px; background: transparent; outline:0px;";
      var addtag_spacer_styles = "width:auto; display:inline-block; white-space:pre; padding:5px 10px; color:transparent; ";
      
      var markup = '<div class="addtag" style="'+addtag_styles+'">'
                 + '<input style="'+addtag_input_styles+'" value="" placeholder="'+( $this.attr("placeholder") || settings.placeholder )+'" maxlength="'+( $this.attr("maxlength") || settings.maxlength )+'" />'
                 + '<div style="'+addtag_spacer_styles+'" class="spacer">&nbsp;</div></div>';
      $this.html(markup);
           
      var $tagedit;
      var tagslist = [];
      
      var $tagedit = $this.find("input");
      var $spacer = $this.find("div.spacer"); 
      
      $this.on('click',function(event) {$tagedit.focus() });
              
      // if user types a comma, create a new tag
      $tagedit.on('keypress',function(event) {
        if (event.which==settings.delimiter.charCodeAt(0) || event.which==13 ) 
        {
          event.preventDefault();
          if( $tagedit.val().length )
            addTag($tagedit.val(),true);
          return false;
        } 
      }).on("input paste",function() {
        $this.find("div.spacer").text($tagedit.val() || ' ');
        if(settings.unique)
          $tagedit.toggleClass('invalid', tagExist($tagedit.val()));
      });
      
      //delete last tag on backspace
      if(settings.backspace) 
        $tagedit.on('keydown', function(event)
        {
          if(event.keyCode == 8 && $(this).val() == '' && tagslist.length)
          {
             event.preventDefault();
             var last = tagslist[ tagslist.length - 1 ];
             removeTag(last);
             $this.trigger('focus');
          }
        });
      
      if ($this.attr('value'))
        initByString($this.attr('value'));

      function addTag(value,setFocus) 
      {
        value = jQuery.trim(value);
        
        var isValid = value && settings.validator(value);
        
        if ( isValid && options.unique)
          isValid = !tagExist(value);
          
        if(!isValid) {
          $tagedit.addClass('invalid');
          if( setFocus ) $tagedit.focus();
          return;
        }
        
        tagslist.push(value);
        var $span = renderTag(value);

        if($tagedit) {
          $tagedit.val('');
          $this.find("div.spacer").html("&nbsp;");
          if (setFocus) $tagedit.focus();
          else $tagedit.blur();
        }          
        
        $this.trigger("change");
        
        return false;
      }
      
      function renderTag(value) 
      {
        return $('<span>').addClass('tag').append(
            $('<span>').text(value),
            $('<a href="javascript:void(0)" title="delete">&#215;</a>').click( function () {  return removeTag(escape(value)); })
        ).insertBefore($this.find('.addtag'));
      }
      
      function initByString(val) {			
        $this.attr('value','');
        tagslist = val.split(settings.delimiter);
        renderTags();
        if(settings.onChange)
          settings.onChange.call($this[0], tagslist);
      }
      
      function renderTags() {			
        $this.find('.tag').remove();
        for (var i=0; i < tagslist.length; i++)
          renderTag(tagslist[i],{focus:false,callback:false});
      }
      
      function tagExist(val) {
        return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not
      }
      
      function removeTag(value) { 

				var old = tagslist;
        tagslist = [];
				
        $this.find('.tag').remove();
        for (var i = 0; i < old.length; i++) 
          if (old[i]!=value) tagslist.push(old[i]);

        renderTags();
        $this.trigger("change");
      	return false;
      }

      Object.defineProperty(this, "value", 
                             { get : function() { return tagslist; },
                               set : function(v) { tagslist = $.isArray(v)? v:[v]; renderTags(v); },
                               enumerable : true,
                               configurable : true});     

      Object.defineProperty(this, "validator", 
                             { get : function() { return settings.validator; },
                               set : function(v) { if(typeof v == "function") settings.validator = v; },
                               enumerable : true,
                               configurable : true});     
      
    } 
    
    this.each(init);
      
    return this;
  
  };

})(jQuery);
